package com.orange.core.service.order;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.orange.core.mapper.floor.FloorMapper;
import com.orange.core.mapper.order.CreditOrderMapper;
import com.orange.core.mapper.shop.ShopMapper;
import com.orange.core.mapper.user.UserMapper;
import com.orange.core.pojo.PageReqDto;
import com.orange.core.pojo.entity.credit.CreditFlow;
import com.orange.core.pojo.entity.floor.Floor;
import com.orange.core.pojo.entity.order.CreditOrder;
import com.orange.core.pojo.entity.product.CreditProduct;
import com.orange.core.pojo.entity.shop.Shop;
import com.orange.core.pojo.entity.user.User;
import com.orange.core.pojo.enums.EFlowBusinessType;
import com.orange.core.pojo.enums.EFlowType;
import com.orange.core.pojo.enums.EPayWay;
import com.orange.core.pojo.enums.ERefundApplyStatus;
import com.orange.core.pojo.enums.ERefundStatus;
import com.orange.core.pojo.enums.EWriteOffStatus;
import com.orange.core.pojo.enums.EWriteOffType;
import com.orange.core.pojo.req.order.CreditOrderPageReq;
import com.orange.core.pojo.req.order.CreditOrderRefundReq;
import com.orange.core.pojo.req.order.CreditShopCartExchangeReq;
import com.orange.core.pojo.req.writeoff.WriteOffSaveReq;
import com.orange.core.pojo.rsp.writeoff.WriteOffResultRsp;
import com.orange.core.service.credit.CreditFlowService;
import com.orange.core.service.generator.CodeGenerateService;
import com.orange.core.service.product.CreditProductService;
import com.orange.core.service.user.UserService;
import com.orange.core.service.writeoff.WriteOffSaveService;
import com.orange.core.util.Constants;
import com.orange.core.util.PrincipalUtil;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
public class CreditOrderService extends ServiceImpl<CreditOrderMapper, CreditOrder> {

    private static final String CREDIT_ORDER_SERIAL = "credit_order_serial";

    private final CreditProductService creditProductService;
    private final UserService userService;
    private final CreditFlowService creditFlowService;
    private final WriteOffSaveService writeOffSaveService;
    private final CodeGenerateService codeGenerateService;
    private final UserMapper userMapper;
    private final ShopMapper shopMapper;
    private final FloorMapper floorMapper;

    public Page<CreditOrder> page(PageReqDto<CreditOrderPageReq> req) {
        if (req.getCondition() == null) {
            req.setCondition(new CreditOrderPageReq());
        }
        CreditOrderPageReq condition = req.getCondition();
        Page<CreditOrder> page = this.lambdaQuery()
                .eq(condition.getUserId() != null, CreditOrder::getUserId, condition.getUserId())
                .eq(condition.getWriteOffStatus() != null, CreditOrder::getWriteOffStatus, condition.getWriteOffStatus())
                .eq(condition.getRefundStatus() != null, CreditOrder::getRefundStatus, condition.getRefundStatus())
                .eq(condition.getRefundApplyStatus() != null, CreditOrder::getRefundApplyStatus, condition.getRefundApplyStatus())
                .orderByDesc(CreditOrder::getId)
                .page(req.toPage());
        List<CreditOrder> records = page.getRecords();
        if (CollUtil.isNotEmpty(records)) {
            List<Integer> userIds = records.stream().map(CreditOrder::getUserId).distinct().toList();
            Map<Integer, String> userPhoneMap = userMapper.selectBatchIds(userIds)
                    .stream().collect(Collectors.toMap(User::getId, User::getPhone));
            records.forEach(order -> order.setPhone(userPhoneMap.getOrDefault(order.getUserId(), "")));
        }
        return page;
    }

    public CreditOrder info(int id) {
        CreditOrder creditOrder = this.getById(id);
        Shop shop = shopMapper.selectById(creditOrder.getShopId());
        Floor floor = floorMapper.selectById(shop.getFloorId());
        creditOrder.setShopNumber(shop.getShopNumber());
        creditOrder.setFloorId(floor.getId());
        creditOrder.setFloorName(floor.getFloorName());
        return creditOrder;
    }

    /**
     * 积分商品兑换
     */
    @Transactional
    public Integer exchange(CreditShopCartExchangeReq req) {
        CreditProduct creditProduct = creditProductService.getById(req.getCreditProductId());
        Assert.notNull(creditProduct, "兑换的商品不存在");
        Assert.isTrue(creditProduct.getStock() >= req.getCount(), "商品库存不足");

        User user = userService.getById(PrincipalUtil.getId());
        Assert.notNull(user, "身份验证失败，请重新登录");

        int totalCredit = creditProduct.getCredit() * req.getCount();
        Assert.isTrue(user.getCredit() >= totalCredit, "积分不足");

        Assert.isTrue(userMapper.deductCredit(user.getId(), totalCredit), "扣减积分失败，积分不足");
        Assert.isTrue(creditProductService.deductStock(req.getCreditProductId(), req.getCount()), "扣减库存失败，库存不足");

        String writeOffNo;
        CreditOrder creditOrder;
        do {
            writeOffNo = Constants.CREDIT_ORDER_PREFIX + RandomStringUtils.randomAlphanumeric(11).toUpperCase();
            creditOrder = this.lambdaQuery()
                    .eq(CreditOrder::getWriteOffNo, writeOffNo)
                    .one();
        } while (creditOrder != null);

        Shop shop = shopMapper.selectById(creditProduct.getShopId());

        // 保存积分订单
        creditOrder = new CreditOrder()
                .setOrderNo(codeGenerateService.createTimestampCode("PO", CREDIT_ORDER_SERIAL))
                .setUserId(PrincipalUtil.getId())
                .setCreditProductId(req.getCreditProductId())
                .setShopId(creditProduct.getShopId())
                .setProductName(creditProduct.getName())
                .setProductCoverImage(creditProduct.getCoverImage())
                .setProductPics(creditProduct.getPics())
                .setProductCredit(creditProduct.getCredit())
                .setProductPrice(creditProduct.getPrice())
                .setProductShopName(shop.getName())
                .setProductShopAddress(shop.getAddress())
                .setQuantity(req.getCount())
                .setPaidCredit(totalCredit)
                .setPayWay(EPayWay.CREDIT.getValue())
                .setWriteOffNo(writeOffNo)
                .setRefundStatus(ERefundStatus.NOT_REFUNDED.getValue());
        this.save(creditOrder);

        // 保存积分流水
        creditFlowService.save(new CreditFlow()
                .setUserId(PrincipalUtil.getId())
                .setBusinessType(EFlowBusinessType.CREDIT_ORDER.getValue())
                .setBusinessId(creditOrder.getId())
                .setFlowType(EFlowType.CONSUME.getValue())
                .setCredit(-totalCredit));

        return creditOrder.getId();
    }

    /**
     * 核销
     */
    @Transactional
    public WriteOffResultRsp writeOff(String writeOffNo) {
        CreditOrder creditOrder = this.lambdaQuery()
                .eq(CreditOrder::getWriteOffNo, writeOffNo)
                .one();
        Assert.notNull(creditOrder, "积分订单不存在");
        Assert.equals(creditOrder.getWriteOffStatus(), EWriteOffStatus.PENDING.getValue(), "积分订单已核销");
        Assert.equals(creditOrder.getRefundStatus(), ERefundStatus.NOT_REFUNDED.getValue(), "积分订单已退款，无法核销");

        User merchant = userMapper.selectById(PrincipalUtil.getId());
        Assert.equals(creditOrder.getShopId(), merchant.getShopId(), "无权核销该积分订单");

        this.lambdaUpdate()
                .set(CreditOrder::getWriteOffStatus, EWriteOffStatus.VERIFIED.getValue())
                .set(CreditOrder::getWriteOffTime, LocalDateTime.now())
                .set(CreditOrder::getWriteOffBy, PrincipalUtil.getId())
                .eq(CreditOrder::getId, creditOrder.getId())
                .update();

        User user = userMapper.selectById(PrincipalUtil.getId());
        writeOffSaveService.writeOff(WriteOffSaveReq.builder()
                .businessId(creditOrder.getId())
                .businessType(EWriteOffType.CREDIT_ORDER.getValue())
                .userId(creditOrder.getUserId())
                .shopId(user.getShopId())
                .build());

        return WriteOffResultRsp.builder()
                .id(creditOrder.getId())
                .writeOffType(EWriteOffType.CREDIT_ORDER.getValue())
                .writeOffNo(creditOrder.getWriteOffNo())
                .writeOffTime(creditOrder.getWriteOffTime())
                .productName(creditOrder.getProductName())
                .quantity(creditOrder.getQuantity())
                .paidCredit(creditOrder.getPaidCredit())
                .build();
    }

    /**
     * 申请退款（只提交申请，不退积分）
     */
    @Transactional
    public void applyRefund(CreditOrderRefundReq req) {
        CreditOrder creditOrder = this.getById(req.getId());
        Assert.notNull(creditOrder, "积分订单不存在");
        Assert.equals(creditOrder.getUserId(), PrincipalUtil.getId(), "无权操作该积分订单");
        Assert.equals(creditOrder.getWriteOffStatus(), EWriteOffStatus.PENDING.getValue(), "积分订单已核销，无法申请退款");
        Assert.equals(creditOrder.getRefundStatus(), ERefundStatus.NOT_REFUNDED.getValue(), "积分订单已退款");
        Assert.isTrue(creditOrder.getRefundApplyStatus() == null || creditOrder.getRefundApplyStatus().equals(ERefundApplyStatus.NOT_APPLIED.getValue()) ||
                        creditOrder.getRefundApplyStatus().equals(ERefundApplyStatus.REJECTED.getValue()) || creditOrder.getRefundApplyStatus().equals(ERefundApplyStatus.CANCELED.getValue()),
                "已申请退款，请勿重复申请");
        this.lambdaUpdate()
                .set(CreditOrder::getRefundApplyStatus, 1)
                .set(CreditOrder::getRefundReason, req.getRefundReason())
                .set(CreditOrder::getRefundRemark, req.getRefundRemark())
                .set(CreditOrder::getRefundApplyTime, LocalDateTime.now())
                .eq(CreditOrder::getId, creditOrder.getId())
                .update();
    }

    /**
     * 审核退款申请（审核通过才退积分）
     */
    @Transactional
    public void auditRefund(Integer id, Integer auditStatus, String auditRemark) {
        CreditOrder creditOrder = this.getById(id);
        Assert.notNull(creditOrder, "积分订单不存在");
        Assert.equals(creditOrder.getRefundApplyStatus(), ERefundApplyStatus.PENDING.getValue(), "退款申请状态异常");
        Assert.equals(creditOrder.getRefundStatus(), ERefundStatus.NOT_REFUNDED.getValue(), "积分订单已退款");
        this.lambdaUpdate()
                .set(CreditOrder::getRefundApplyStatus, auditStatus)
                .set(CreditOrder::getAuditBy, PrincipalUtil.getId())
                .set(CreditOrder::getAuditTime, LocalDateTime.now())
                .set(CreditOrder::getAuditRemark, auditRemark)
                .eq(CreditOrder::getId, creditOrder.getId())
                .update();
        if (auditStatus.equals(2)) { // 审核通过
            // 退还积分
            Assert.isTrue(userMapper.addCredit(creditOrder.getUserId(), creditOrder.getPaidCredit()), "退还积分失败");
            // 退还库存
            Assert.isTrue(creditProductService.addStock(creditOrder.getCreditProductId(), creditOrder.getQuantity()), "退还库存失败");
            // 保存积分流水
            creditFlowService.save(new CreditFlow()
                    .setUserId(creditOrder.getUserId())
                    .setBusinessType(EFlowBusinessType.CREDIT_ORDER_REFUND.getValue())
                    .setBusinessId(creditOrder.getId())
                    .setFlowType(EFlowType.OBTAIN.getValue())
                    .setCredit(creditOrder.getPaidCredit()));
            // 更新订单退款状态
            this.lambdaUpdate()
                    .set(CreditOrder::getRefundStatus, ERefundStatus.REFUNDED.getValue())
                    .set(CreditOrder::getRefundTime, LocalDateTime.now())
                    .set(CreditOrder::getRefundBy, PrincipalUtil.getId())
                    .eq(CreditOrder::getId, creditOrder.getId())
                    .update();
        }
    }
}
